//-------------------------------------------------------------------
//	Copyright (c) 2010-2014  Zhirnov Andrey
//	This file is part of the "UX Standard Lib" project.
//	See copyright notice in "UX_STL_Core.h".
//-------------------------------------------------------------------

#ifndef _UXS_RADIANS_AND_DEGREES_DIMENSIONS_H
#define _UXS_RADIANS_AND_DEGREES_DIMENSIONS_H

#pragma once

#include "Helpers.h"
#include "../CompileTime/TemplateMath.h"
#include "../Math/MathTypes.h"

namespace UX_STL
{
namespace UXTypes
{

	template <typename T>	struct TRadians;
	template <typename T>	struct TDegrees;


	typedef TRadians< UXMath::real >		Radians;
	typedef TDegrees< UXMath::real >		Degrees;



	//
	// Radians
	//

	template <typename T>
	struct TRadians : TypeDescriptor::MustBeFloat<T>
	{
	// types
	public:
		typedef TRadians<T>		Self;
		typedef T				value_t;


	// variables
	private:
		T	_value;


	// methods
	public:
		TRadians () : _value(0) {}

		explicit
		TRadians (T value) : _value(value) {}

		template <typename B>
		explicit
		TRadians (TDegrees<B> deg);
		
		operator T () const				{ return _value; }

		T &			ref ()				{ return _value; }
		T const &	ref ()		const	{ return _value; }

		TDegrees<T> ToDegrees () const;
		
		template <typename B>
		TDegrees<B> ToDegrees () const;

		template <typename B>
		TRadians<B> To () const;

		Self	ToInterval (Self minValue, Self maxValue) const;

		string	ToString ()		const			{ return string() << _value << " rad"; }

		_UX_DIM_ALL_FLOAT_OPERATORS_SELF( _value );

		static Self	Pi (T scale)				{ return Self( UXMath::Pi<T>() * scale ); }		// Pi * x
		static Self	Pi ()						{ return Self( UXMath::Pi<T>() ); }				// Pi
		static Self ReciporalPi ()				{ return Self( UXMath::ReciporalPi<T>() ); }	// 1/Pi
		static Self	DoublePi ()					{ return Self( Pi() * T(2) ); }					// 2*Pi
		static Self	HalfPi ()					{ return Self( Pi() * T(0.5) ); }				// Pi/2

		static Self FromArcminutes (T value);
		static Self FromArcseconds (T value);
		
		static Self From (T deg, T arcmin = T(0), T arcsec = T(0));
	};



	//
	// Degrees
	//

	template <typename T>
	struct TDegrees : TypeDescriptor::MustBeFloat<T>
	{
	// types
	public:
		typedef TDegrees<T>		Self;
		typedef T				value_t;


	// variables
	private:
		T	_value;


	// methods
	public:
		TDegrees () : _value(0) {}

		explicit
		TDegrees (T value) : _value(value) {}

		template <typename B>
		explicit
		TDegrees (TRadians<B> rad);
		
		operator T () const				{ return _value; }

		T &			ref ()				{ return _value; }
		T const &	ref ()		const	{ return _value; }

		TRadians<T> ToRadians () const;

		template <typename B>
		TRadians<B> ToRadians () const;

		template <typename B>
		TDegrees<B> To () const;
		
		Self	ToInterval (Self minValue, Self maxValue) const;
		
		string	ToString ()		const			{ return string() << _value << " deg"; }

		_UX_DIM_ALL_FLOAT_OPERATORS_SELF( _value );
		
		static Self	Pi (T scale)				{ return Self( T(180) * scale ); }				// Pi * x
		static Self	Pi ()						{ return Self( T(180) ); }						// Pi
		static Self ReciporalPi ()				{ return Self( T(0.0055555555555555558) ); }	// 1/Pi
		static Self	DoublePi ()					{ return Self( Pi() * T(2) ); }					// 2*Pi
		static Self	HalfPi ()					{ return Self( Pi() * T(0.5) ); }				// Pi/2
		
		static Self FromArcminutes (T value)	{ return Self( value / T(60.0) ); }				// T(0.01666667)
		static Self FromArcseconds (T value)	{ return Self( value / T(3600.0) ); }			// T(0.0002777778)

		static Self From (T deg, T arcmin = T(0), T arcsec = T(0));
	};

	
	
//--------------------------------- TRadians --------------------------------//

	template <typename T>
	template <typename B>
	inline TRadians<T>::TRadians (TDegrees<B> deg) :
		_value( T(B(deg)) * (T)Pi() * (T)deg.ReciporalPi() )
	{
	}
		
	template <typename T>
	inline TDegrees<T> TRadians<T>::ToDegrees () const
	{
		return TDegrees<T>( *this );
	}
		
	template <typename T>
	template <typename B>
	inline TDegrees<B> TRadians<T>::ToDegrees () const
	{
		return TDegrees<B>( *this );
	}
	
	template <typename T>
	template <typename B>
	inline TRadians<B> TRadians<T>::To () const
	{
		return TRadians<B>( B(_value) );
	}
	
	template <typename T>
	inline TRadians<T>  TRadians<T>::ToInterval (Self minValue, Self maxValue) const
	{
		// 0..+2Pi, 0..+Pi, -Pi..0, -2Pi..0, and other
		ASSERT( minValue < maxValue );
		ASSERT( IsNotZero( (T)minValue ) or IsNotZero( (T)maxValue ) );
		
		const T	period	= Abs( maxValue - minValue );
		const T	val		= UXMath::Mod( _value, period );

		return Self( val > maxValue ? val - period :
					 val < minValue ? val + period :
					 val );
	}
	
	template <typename T>
	inline TRadians<T>  TRadians<T>::FromArcminutes (T value)
	{
		return TDegrees<T>::FromArcminutes( value ).ToRadians();
	}
	
	template <typename T>
	inline TRadians<T>  TRadians<T>::FromArcseconds (T value)
	{
		return TDegrees<T>::FromArcseconds( value ).ToRadians();
	}
	
	template <typename T>
	inline TRadians<T>  TRadians<T>::From (T deg, T arcmin, T arcsec)
	{
		return TDegrees<T>( deg + (arcmin / T(60.0)) + (arcsec / T(3600.0)) ).ToRadians();
	}

	

//--------------------------------- TDegrees --------------------------------//

	template <typename T>
	template <typename B>
	inline TDegrees<T>::TDegrees (TRadians<B> rad) :
		_value( T(B(rad)) * (T)Pi() * (T)rad.ReciporalPi() )
	{
	}
		
	template <typename T>
	inline TRadians<T> TDegrees<T>::ToRadians () const
	{
		return TRadians<T>( *this );
	}
	
	template <typename T>
	template <typename B>
	inline TRadians<B> TDegrees<T>::ToRadians () const
	{
		return TRadians<B>( *this );
	}
	
	template <typename T>
	template <typename B>
	inline TDegrees<B> TDegrees<T>::To () const
	{
		return TDegrees<B>( B(_value) );
	}
	
	template <typename T>
	inline TDegrees<T>  TDegrees<T>::ToInterval (Self minValue, Self maxValue) const
	{
		// 0..+2Pi, 0..+Pi, -Pi..0, -2Pi..0, and other
		ASSERT( minValue < maxValue );
		ASSERT( IsNotZero( (T)minValue ) or IsNotZero( (T)maxValue ) );
		
		const T	period	= Abs( maxValue - minValue );
		const T	val		= UXMath::Mod( _value, period );

		return Self( val > maxValue ? val - period :
					 val < minValue ? val + period :
					 val );
	}
	
	template <typename T>
	inline TDegrees<T>  TDegrees<T>::From (T deg, T arcmin, T arcsec)
	{
		return TDegrees<T>( deg + (arcmin / T(60.0)) + (arcsec / T(3600.0)) );
	}


}	// UXTypes
}	// UX_STL

#endif	// _UXS_RADIANS_AND_DEGREES_DIMENSIONS_H
